/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/sync.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../toolkit/xml.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "misc.h"
#include "client.h"
#include "screen.h"
#include "group.h"
#include "config.h"
#include "place.h"
#include "moveresize.h"
#include "debug.h"
#include "frame.h"
static struct wm_rect *pick_pointer_head(struct wm_client *c)
{
	return screen_area(c->desktop, screen_monitor_pointer(), NULL);
}

struct wm_place_head {
	guint monitor;
	guint flags;
};
enum {
	HEAD_PARENT = 1 << 0,
	HEAD_PLACED = 1 << 1,
	HEAD_PRIMARY = 1 << 2,
	HEAD_GROUP_DESK = 1 << 3,
	HEAD_GROUP = 1 << 4
};

static gint cmp_foreground(const void *a, const void *b)
{
	const struct wm_place_head *h1 = a;
	const struct wm_place_head *h2 = b;
	gint i = 0;
	if(h1->monitor == h2->monitor)
		return 0;
	if(h1->flags & HEAD_PARENT)
		--i;
	if(h2->flags & HEAD_PARENT)
		++i;
	if(i)
		return i;
	if(h1->flags & HEAD_PLACED)
		--i;
	if(h2->flags & HEAD_PLACED)
		++i;
	if(i)
		return i;
	if(h1->flags & HEAD_PRIMARY)
		--i;
	if(h2->flags & HEAD_PRIMARY)
		++i;
	if(i)
		return i;
	if(h1->flags & HEAD_GROUP_DESK)
		--i;
	if(h2->flags & HEAD_GROUP_DESK)
		++i;
	if(i)
		return i;
	if(h1->flags & HEAD_GROUP)
		--i;
	if(h2->flags & HEAD_GROUP)
		++i;
	if(i)
		return i;
	return h1->monitor - h2->monitor;
}

static gint cmp_background(const void *a, const void *b)
{
	const struct wm_place_head *h1 = a;
	const struct wm_place_head *h2 = b;
	gint i = 0;
	if(h1->monitor == h2->monitor)
		return 0;
	if(h1->flags & HEAD_PARENT)
		--i;
	if(h2->flags & HEAD_PARENT)
		++i;
	if(i)
		return i;
	if(h1->flags & HEAD_GROUP_DESK || h2->flags & HEAD_GROUP_DESK) {
		if(h1->flags & HEAD_GROUP_DESK)
			--i;
		if(h2->flags & HEAD_GROUP_DESK)
			++i;
		if(i)
			return i;
		if(h1->flags & HEAD_PRIMARY)
			--i;
		if(h2->flags & HEAD_PRIMARY)
			++i;
		if(i)
			return i;
	}
	if(h1->flags & HEAD_GROUP || h2->flags & HEAD_GROUP) {
		if(h1->flags & HEAD_GROUP)
			--i;
		if(h2->flags & HEAD_GROUP)
			++i;
		if(i)
			return i;
		if(h1->flags & HEAD_PRIMARY)
			--i;
		if(h2->flags & HEAD_PRIMARY)
			++i;
		if(i)
			return i;
	}
	if(h1->flags & HEAD_PRIMARY)
		--i;
	if(h2->flags & HEAD_PRIMARY)
		++i;
	if(i)
		return i;
	return h1->monitor - h2->monitor;
}

static struct wm_rect *pick_head(struct wm_client *c, gboolean foreground)
{
	struct wm_rect *area;
	struct wm_place_head *choice;
	guint i;
	struct wm_client *p;
	GSList *it;
	choice = g_new(struct wm_place_head, screen_num_monitors);
	for(i = 0; i < screen_num_monitors; ++i) {
		choice[i].monitor = i;
		choice[i].flags = 0;
	}
	if(c->group) {
		for(it = c->group->members; it; it = g_slist_next(it)) {
			struct wm_client *itc = it->data;
			if(itc != c) {
				guint m = client_monitor(itc);
				if(m < screen_num_monitors) {
					if(screen_compare_desktops(itc->desktop, c->desktop))
						choice[m].flags |= HEAD_GROUP_DESK;
					else
						choice[m].flags |= HEAD_GROUP;
				}
			}
		}
	}
	i = screen_monitor_primary(FALSE);
	if(i < screen_num_monitors) {
		choice[i].flags |= HEAD_PRIMARY;
		if(config_place_monitor == WM_PLACE_MONITOR_PRIMARY)
			choice[i].flags |= HEAD_PLACED;
	}
	if((p = client_direct_parent(c))) {
		i = client_monitor(p);
		if(i < screen_num_monitors)
			choice[i].flags |= HEAD_PARENT;
	}
	qsort(choice, screen_num_monitors, sizeof(struct wm_place_head), foreground ? cmp_foreground : cmp_background);
	for(i = 0; i < screen_num_monitors; ++i) {
		wm_debug("placement choice %d is monitor %d", i, choice[i].monitor);
		if(choice[i].flags & HEAD_PARENT)
			wm_debug("  - parent on monitor");
		if(choice[i].flags & HEAD_PLACED)
			wm_debug("  - placement choice");
		if(choice[i].flags & HEAD_PRIMARY)
			wm_debug("  - primary monitor");
		if(choice[i].flags & HEAD_GROUP_DESK)
			wm_debug("  - group on same desktop");
		if(choice[i].flags & HEAD_GROUP)
			wm_debug("  - group on other desktop");
	}
	area = screen_area(c->desktop, choice[0].monitor, NULL);
	g_free(choice);
	return area;
}

static gboolean place_random(struct wm_client *client, struct wm_rect *area, gint *x, gint *y)
{
	gint l, r, t, b;
	wm_debug("placing randomly");
	l = area->x;
	t = area->y;
	r = area->x + area->width - client->frame->area.width;
	b = area->y + area->height - client->frame->area.height;
	if(r > l)
		*x = g_random_int_range(l, r + 1);
	else
		*x = area->x;
	if(b > t)
		*y = g_random_int_range(t, b + 1);
	else
		*y = area->y;
	return TRUE;
}

static GSList *area_add(GSList *list, struct wm_rect *a)
{
	struct wm_rect *r = g_slice_new(struct wm_rect);
	*r = *a;
	return g_slist_prepend(list, r);
}

static GSList *area_remove(GSList *list, struct wm_rect *a)
{
	GSList *sit;
	GSList *result = NULL;
	for(sit = list; sit; sit = g_slist_next(sit)) {
		struct wm_rect *r = sit->data;
		if(!RECT_INTERSECTS_RECT(*r, *a)) {
			result = g_slist_prepend(result, r);
		} else {
			struct wm_rect isect, extra;
			RECT_SET_INTERSECTION(isect, *r, *a);
			if(RECT_LEFT(isect) > RECT_LEFT(*r)) {
				RECT_SET(extra, r->x, r->y, RECT_LEFT(isect) - r->x, r->height);
				result = area_add(result, &extra);
			}
			if(RECT_TOP(isect) > RECT_TOP(*r)) {
				RECT_SET(extra, r->x, r->y, r->width, RECT_TOP(isect) - r->y + 1);
				result = area_add(result, &extra);
			}
			if(RECT_RIGHT(isect) < RECT_RIGHT(*r)) {
				RECT_SET(extra, RECT_RIGHT(isect) + 1, r->y, RECT_RIGHT(*r) - RECT_RIGHT(isect), r->height);
				result = area_add(result, &extra);
			}
			if(RECT_BOTTOM(isect) < RECT_BOTTOM(*r)) {
				RECT_SET(extra, r->x, RECT_BOTTOM(isect) + 1, r->width, RECT_BOTTOM(*r) - RECT_BOTTOM(isect));
				result = area_add(result, &extra);
			}
			g_slice_free(struct wm_rect, r);
	}} g_slist_free(list);
	return result;
}
enum {
	IGNORE_FULLSCREEN = 1,
	IGNORE_MAXIMIZED = 2,
	IGNORE_NONGROUP = 3,
	IGNORE_BELOW = 4,
	IGNORE_END = 5
};
static gboolean place_nooverlap(struct wm_client *c, struct wm_rect *area, gint *x, gint *y)
{
	gint ignore;
	gboolean ret;
	gint maxsize;
	GSList *spaces = NULL, *sit, *maxit;
	wm_debug("placing nonoverlap");
	ret = FALSE;
	maxsize = 0;
	maxit = NULL;
	for(ignore = 0; ignore < IGNORE_END && !ret; ignore++) {
		GList *it;
		spaces = area_add(spaces, area);
		for(it = client_list; it; it = g_list_next(it)) {
			struct wm_client *test = it->data;
			if(screen_showing_desktop)
				continue;
			if(c == test)
				continue;
			if(c->desktop != DESKTOP_ALL) {
				if(test->desktop != c->desktop && test->desktop != DESKTOP_ALL)
					continue;
			} else {
				if(test->desktop != screen_desktop && test->desktop != DESKTOP_ALL)
					continue;
			}
			if(test->type == WM_CLIENT_TYPE_DESKTOP)
				continue;
			if((ignore >= IGNORE_FULLSCREEN) && test->fullscreen)
				continue;
			if((ignore >= IGNORE_MAXIMIZED) && test->max_horz && test->max_vert)
				continue;
			if((ignore >= IGNORE_NONGROUP) && client_has_group_siblings(c) && test->group != c->group)
				continue;
			if((ignore >= IGNORE_BELOW) && test->layer < c->layer)
				continue;
			spaces = area_remove(spaces, &test->frame->area);
		}
		for(sit = spaces; sit; sit = g_slist_next(sit)) {
			struct wm_rect *r = sit->data;
			if(r->width >= c->frame->area.width && r->height >= c->frame->area.height && r->width * r->height > maxsize) {
				maxsize = r->width * r->height;
				maxit = sit;
			}
		}
		if(maxit) {
			struct wm_rect *r = maxit->data;
			*x = r->x;
			*y = r->y;
			if(config_place_center) {
				*x += (r->width - c->frame->area.width) / 2;
				*y += (r->height - c->frame->area.height) / 2;
			}
			ret = TRUE;
		}
		while(spaces) {
			g_slice_free(struct wm_rect, spaces->data);
			spaces = g_slist_delete_link(spaces, spaces);
		}
	}
	return ret;
}

static gboolean place_under_mouse(struct wm_client *client, gint *x, gint *y)
{
	gint l, r, t, b;
	gint px, py;
	struct wm_rect *area;
	wm_debug("placing under mouse");
	if(!screen_pointer_pos(&px, &py))
		return FALSE;
	area = pick_pointer_head(client);
	l = area->x;
	t = area->y;
	r = area->x + area->width - client->frame->area.width;
	b = area->y + area->height - client->frame->area.height;
	*x = px - client->area.width / 2 - client->frame->size.left;
	*x = MIN(MAX(*x, l), r);
	*y = py - client->area.height / 2 - client->frame->size.top;
	*y = MIN(MAX(*y, t), b);
	g_slice_free(struct wm_rect, area);
	return TRUE;
}

static gboolean place_per_app_setting(struct wm_client *client, gint *x, gint *y, struct wm_app_settings *settings)
{
	struct wm_rect *screen = NULL;
	if(!settings || (settings && !settings->pos_given))
		return FALSE;
	wm_debug("placing by per-app settings");
	if(settings->monitor == 0)
		screen = pick_pointer_head(client);
	else {
		guint m = settings->monitor;
		if(m < 1 || m > screen_num_monitors)
			m = screen_monitor_primary(TRUE) + 1;
		screen = screen_area(client->desktop, m - 1, NULL);
	}
	if(settings->position.x.center)
		*x = screen->x + screen->width / 2 - client->area.width / 2;
	else if(settings->position.x.opposite)
		*x = screen->x + screen->width - client->frame->area.width - settings->position.x.pos;
	else
		*x = screen->x + settings->position.x.pos;
	if(settings->position.x.denom)
		*x = (*x * screen->width) / settings->position.x.denom;
	if(settings->position.y.center)
		*y = screen->y + screen->height / 2 - client->area.height / 2;
	else if(settings->position.y.opposite)
		*y = screen->y + screen->height - client->frame->area.height - settings->position.y.pos;
	else
		*y = screen->y + settings->position.y.pos;
	if(settings->position.y.denom)
		*y = (*y * screen->height) / settings->position.y.denom;
	g_slice_free(struct wm_rect, screen);
	return TRUE;
}

static gboolean place_transient_splash(struct wm_client *client, struct wm_rect *area, gint *x, gint *y)
{
	if(client->type == WM_CLIENT_TYPE_DIALOG) {
		GSList *it;
		gboolean first = TRUE;
		gint l, r, t, b;
		wm_debug("placing dialog");
		for(it = client->parents; it; it = g_slist_next(it)) {
			struct wm_client *m = it->data;
			if(first) {
				l = RECT_LEFT(m->frame->area);
				t = RECT_TOP(m->frame->area);
				r = RECT_RIGHT(m->frame->area);
				b = RECT_BOTTOM(m->frame->area);
				first = FALSE;
			} else {
				l = MIN(l, RECT_LEFT(m->frame->area));
				t = MIN(t, RECT_TOP(m->frame->area));
				r = MAX(r, RECT_RIGHT(m->frame->area));
				b = MAX(b, RECT_BOTTOM(m->frame->area));
			}
			if(!first) {
				*x = ((r + 1 - l) - client->frame->area.width) / 2 + l;
				*y = ((b + 1 - t) - client->frame->area.height) / 2 + t;
				return TRUE;
			}
		}
	}
	if(client->type == WM_CLIENT_TYPE_DIALOG) {
		wm_debug("placing dialog");
		*x = (area->width - client->frame->area.width) / 2 + area->x;
		*y = (area->height - client->frame->area.height) / 2 + area->y;
		return TRUE;
	}
	return FALSE;
}

gboolean place_client(struct wm_client *client, gboolean foreground, gint *x, gint *y, struct wm_app_settings *settings)
{
	struct wm_rect *area;
	gboolean ret;
	if(((client->positioned & USPosition) && !(settings && settings->pos_given && settings->pos_force))
		|| ((client->positioned & PPosition) && !(settings && settings->pos_given)))
		return FALSE;
	area = pick_head(client, foreground);
	ret = place_per_app_setting(client, x, y, settings) || place_transient_splash(client, area, x, y)
		|| (config_place_policy == WM_PLACE_POLICY_MOUSE && place_under_mouse(client, x, y))
		|| place_nooverlap(client, area, x, y) || place_random(client, area, x, y);
	g_assert(ret);
	g_slice_free(struct wm_rect, area);
	return TRUE;
}
